
Type CalBone

	Field m_coreBone:CalCoreBone
	Field m_skeleton:CalSkeleton
	Field m_accumulatedWeight:Float
	Field m_accumulatedWeightAbsolute:Float
	Field m_translation:CalVector
	Field m_rotation:CalQuaternion
	Field m_translationAbsolute:CalVector
	Field m_rotationAbsolute:CalQuaternion
	Field m_translationBoneSpace:CalVector
	Field m_rotationBoneSpace:CalQuaternion
	Field m_transformMatrix:CalMatrix
	Field m_boundingBox:CalBoundingBox
	
	Method New()
		 m_coreBone = Null
		 m_skeleton = Null
		 m_accumulatedWeight = 0
		 m_accumulatedWeightAbsolute = 0
		 m_translation = New CalVector
		 m_rotation = New CalQuaternion
		 m_translationAbsolute = New CalVector
		 m_rotationAbsolute = New CalQuaternion
		 m_translationBoneSpace = New CalVector
		 m_rotationBoneSpace = New CalQuaternion
		 m_transformMatrix = New CalMatrix
		 m_boundingBox = New CalBoundingBox	
	End Method
	
	
	Function FromCore:CalBone(coreBone:CalCoreBone)
		Local bone:CalBone = New CalBone
		 bone.m_coreBone = coreBone
		 bone.m_skeleton = Null
		 bone.m_accumulatedWeight = 0
		 bone.m_accumulatedWeightAbsolute = 0
		 bone.m_translation = New CalVector
		 bone.m_rotation = New CalQuaternion
		 bone.m_translationAbsolute = New CalVector
		 bone.m_rotationAbsolute = New CalQuaternion
		 bone.m_translationBoneSpace = New CalVector
		 bone.m_rotationBoneSpace = New CalQuaternion
		 bone.m_transformMatrix = New CalMatrix
		 bone.m_boundingBox = New CalBoundingBox
		
		bone.clearState()
		Return bone
		
	End Function


	Method blendState(weight:Float, translation:CalVector, rotation:CalQuaternion)
		If m_accumulatedWeightAbsolute = 0 Then
			' it is the first state, so just copy it into the bone state
			m_translationAbsolute.assignvector(translation)
			m_rotationAbsolute.assign(rotation)
			m_accumulatedWeightAbsolute = weight
		
		Else 
			' it is not the first state, so blend all attributes
			Local factor:Float = weight / (m_accumulatedWeightAbsolute + weight)
	
			m_translationAbsolute.blend(factor, translation)
			m_rotationAbsolute.blend(factor, rotation)
			m_accumulatedWeightAbsolute = m_accumulatedWeightAbsolute+weight
		EndIf
		
	End Method
	
	Method calculateState()
		' check if the bone was not touched by any active animation
		If m_accumulatedWeight = 0 Then
			' set the bone to the initial skeleton state
			m_translation.assignvector(m_coreBone.getTranslation())
			m_rotation.assign(m_coreBone.GetRotation())
		EndIf
	
		' get parent bone id
		Local parentId:Int = m_coreBone.getParentId()
	
		If parentId = -1 Then
			' no parent, this means absolute state == relative state
			m_translationAbsolute.assignvector(m_translation)
			m_rotationAbsolute.assign(m_rotation)
		
		Else 
			' get the parent bone
			Local parent:CalBone = m_skeleton.getBone(parentId)
	
			' transform relative state with the absolute state of the parent
			m_translationAbsolute.assignvector(m_translation)
			m_translationAbsolute.multQuaternionLocal(parent.getRotationAbsolute())
			m_translationAbsolute.addLocal(parent.getTranslationAbsolute())
	
			m_rotationAbsolute.assign(m_rotation)
			m_rotationAbsolute.multQuaternionLocal(parent.getRotationAbsolute())
		EndIf
	
		' calculate the bone space transformation
		m_translationBoneSpace.assignvector(m_coreBone.getTranslationBoneSpace())
		m_translationBoneSpace.multQuaternionLocal(m_rotationAbsolute)
		m_translationBoneSpace.addLocal(m_translationAbsolute)
	
		m_rotationBoneSpace.assign(m_coreBone.getRotationBoneSpace())
		m_rotationBoneSpace.multQuaternionLocal(m_rotationAbsolute)
	
		' Generate the vertex transform.  
		'TODO: If I ever add support for bone-scaling to Cal3D, this step will become significantly more complex.
		m_transformMatrix.assign(m_rotationBoneSpace)
	
		' calculate all child bones
		Local listChildId:Int[] = m_coreBone.getListChildId()
		For Local iChildId:Int = 0 To listChildId.length-1
			m_skeleton.getBone(listChildId[iChildId]).calculateState()
		Next	
	End Method
	
	Method clearState()
		m_accumulatedWeight = 0
		m_accumulatedWeightAbsolute = 0
	End Method
	
	Method getCoreBone:CalCoreBone()
		Return m_coreBone
	End Method
	
	Method setCoreState()
		' set the bone to the initial skeleton state
		m_translation.assignvector(m_coreBone.getTranslation())
		m_rotation.assign(m_coreBone.GetRotation())
	
		' set the appropriate weights
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1
	
		calculateState()		
	End Method
	
	Method setCoreStateRecursive()
		' set the bone to the initial skeleton state
		m_translation.assignvector(m_coreBone.getTranslation())
		m_rotation.assign(m_coreBone.GetRotation())
	
		' set the appropriate weights
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1
	
		' set core state for all child bones
		Local listChildId:Int[] = m_coreBone.getListChildId()
		For Local iChildId:Int = 0 To listChildId.length-1
			m_skeleton.getBone(listChildId[iChildId]).setCoreStateRecursive()
		Next
	
		calculateState() 		
	End Method
	
	Method SetRotation(rotation:CalQuaternion)
		m_rotation.assign(rotation)
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1		
	End Method
	
	Method GetRotation:CalQuaternion()
		Return m_rotation
	End Method
	
	Method getRotationAbsolute:CalQuaternion()
		Return m_rotationAbsolute
	End Method
	
	Method getRotationBoneSpace:CalQuaternion()
		Return m_rotationBoneSpace
	End Method
	
	Method setTranslation(translation:CalVector)
		m_translation.assignvector(translation)
		m_accumulatedWeightAbsolute = 1
		m_accumulatedWeight = 1		
	End Method
	
	Method getTranslation:CalVector()
		Return m_translation
	End Method
	
	Method getTranslationAbsolute:CalVector()
		Return m_translationAbsolute
	End	Method

	Method getTranslationBoneSpace:CalVector()
		Return m_translationBoneSpace
	End Method
	
	Method getTransformMatrix:CalMatrix()
		Return m_transformMatrix
	End	Method
	
	Method lockState()
		' clamp accumulated weight
		If m_accumulatedWeightAbsolute > 1 - m_accumulatedWeight Then
			m_accumulatedWeightAbsolute = 1 - m_accumulatedWeight
		EndIf
	
		If m_accumulatedWeightAbsolute > 0 Then
			If m_accumulatedWeight = 0 Then
				' it is the first state, so just copy it into the bone state
				m_translation.assignvector(m_translationAbsolute)
				m_rotation.assign(m_rotationAbsolute)
				m_accumulatedWeight = m_accumulatedWeightAbsolute
			
			Else 
				' it is not the first state, so blend all attributes
				Local factor:Float = m_accumulatedWeightAbsolute / (m_accumulatedWeight + m_accumulatedWeightAbsolute)
	
				m_translation.blend(factor, m_translationAbsolute)
				m_rotation.blend(factor, m_rotationAbsolute)
				m_accumulatedWeight = m_accumulatedWeight+m_accumulatedWeightAbsolute
			EndIf
	
			m_accumulatedWeightAbsolute = 0
		EndIf		
	End Method
	
	Method setSkeleton(skeleton:CalSkeleton)
		m_skeleton = skeleton
	End Method


	Method calculateBoundingBox()
		If Self.getCoreBone().isBoundingBoxPrecomputed() = False Then
			Return
		EndIf
		Local dir:CalVector = CalVector.FromCoords(1, 0, 0)
		dir.multMatrixLocal(Self.getTransformMatrix())
		Self.m_boundingBox.plane[0].setNormal(dir)
	
		dir.assigncoords(-1, 0, 0)
		dir.multMatrixLocal(Self.getTransformMatrix())
		Self.m_boundingBox.plane[1].setNormal(dir)
	
		dir.assigncoords(0, 1, 0)
		dir.multMatrixLocal(Self.getTransformMatrix())
		Self.m_boundingBox.plane[2].setNormal(dir)
	
		dir.assigncoords(0, -1, 0)
		dir.multMatrixLocal(Self.getTransformMatrix())
		Self.m_boundingBox.plane[3].setNormal(dir)
	
		dir.assigncoords(0, 0, 1)
		dir.multMatrixLocal(Self.getTransformMatrix())
		Self.m_boundingBox.plane[4].setNormal(dir)
	
		dir.assigncoords(0, 0, -1)
		dir.multMatrixLocal(Self.getTransformMatrix())
		Self.m_boundingBox.plane[5].setNormal(dir)
	
		Local position:CalVector = New CalVector
		For Local i:Int = 0 To 5
			Self.getCoreBone().getBoundingData(i, position)
	
			position.multMatrixLocal(Self.getTransformMatrix())
			position.addLocal(Self.getTranslationBoneSpace())
	
			For Local planeId:Int = 0 To 5
				If Self.m_boundingBox.plane[planeId].eval(position) < 0 Then
					Self.m_boundingBox.plane[planeId].setPosition(position)
				EndIf
			Next
		Next	
	End Method
	
	Method getBoundingBox:CalBoundingBox()
		Return m_boundingBox
	End Method

End Type
